package com.faner4cloud.yun.common.redis;


import com.faner4cloud.yun.common.redis.cache.RedisCache;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.redisson.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @描述 redis配置
 * @作者 faner
 * @创建时间 2022/4/20 3:46 PM
 */
@Slf4j
@Configuration
@EnableCaching
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {
	@Autowired
	private RedisProperties redisProperties;

	/**
	 * 我们手动实例化redisTemplate，指定序列化方式
	 */
	@SuppressWarnings("all")
	@Bean(name = "redisTemplate")
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
		RedisSerializer<Object> serializer = redisSerializer();
		RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
		redisTemplate.setConnectionFactory(redisConnectionFactory);
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setValueSerializer(serializer);
		redisTemplate.setHashKeySerializer(new StringRedisSerializer());
		redisTemplate.setHashValueSerializer(serializer);
		redisTemplate.afterPropertiesSet();
		return redisTemplate;
	}


	@Bean
	@ConditionalOnClass(RedissonClient.class)
	public RedissonClient redissonClient(){
		Config config = new Config();
		config.useSingleServer()
			.setAddress("redis://"+redisProperties.getHost()+":"+redisProperties.getPort())
			.setPassword(redisProperties.getPassword())
			.setConnectionMinimumIdleSize(10)
			.setConnectionPoolSize(100)
			.setIdleConnectionTimeout(600000)
			.setSubscriptionConnectionMinimumIdleSize(10)
			.setSubscriptionConnectionPoolSize(100)
			.setTimeout(3000);

		config.setCodec(new StringCodec());
		config.setThreads(5);
		config.setNettyThreads(5);

		RedissonClient client = Redisson.create(config);

		return client;
	}

	@Bean
	@ConditionalOnClass(RedisConnectionFactory.class)
	public RedisCache redisCache(RedisTemplate redisTemplate) {
		RedisCache redisCache = new RedisCache(redisTemplate);
		return redisCache;
	}

	@Bean
	@ConditionalOnClass(RedissonClient.class)
	public RedisLock redisLock(RedissonClient redissonClient) {
		return new RedisLock(redissonClient);
	}

	@Bean
	public RedisSerializer<Object> redisSerializer() {
		//创建JSON序列化器
		Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		//必须设置，否则无法将JSON转化为对象，会转化成Map类型
		objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
		// 解决jackson2无法反序列化LocalDateTime的问题
		objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
		serializer.setObjectMapper(objectMapper);
		return serializer;
	}

	/**
	 * 配置缓存管理器
	 * @param redisConnectionFactory
	 * @return
	 */
//	@Bean
//	public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//		// 生成一个默认配置，通过config对象即可对缓存进行自定义配置
//		RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//		// 设置缓存的默认过期时间，也是使用Duration设置
//		config = config.entryTtl(Duration.ofDays(1))
//			// 设置 key为string序列化
//			.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
//			// 设置value为json序列化
//			.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer()))
//			// 不缓存空值
//			.disableCachingNullValues();
//		// 使用自定义的缓存配置初始化一个cacheManager
//		RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
//			// 一定要先调用该方法设置初始化的缓存名，再初始化相关的配置
//			.build();
//		return cacheManager;
//	}


	/**
	 * 如果cache出错， 我们会记录在日志里，方便排查，比如反序列化异常
	 */
	@Bean
	@Override
	public CacheErrorHandler errorHandler() {
		return new RedisConfig.LoggingCacheErrorHandler();
	}


	/* non-public */
	static class LoggingCacheErrorHandler extends SimpleCacheErrorHandler {
		private final Logger logger = LoggerFactory.getLogger(this.getClass());

		@Override
		public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
			logger.error(String.format("cacheName:%s,cacheKey:%s",
				cache == null ? "unknown" : cache.getName(), key), exception);
			super.handleCacheGetError(exception, cache, key);
		}

		@Override
		public void handleCachePutError(RuntimeException exception, Cache cache, Object key,
										Object value) {
			logger.error(String.format("cacheName:%s,cacheKey:%s",
				cache == null ? "unknown" : cache.getName(), key), exception);
			super.handleCachePutError(exception, cache, key, value);
		}

		@Override
		public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
			logger.error(String.format("cacheName:%s,cacheKey:%s",
				cache == null ? "unknown" : cache.getName(), key), exception);
			super.handleCacheEvictError(exception, cache, key);
		}

		@Override
		public void handleCacheClearError(RuntimeException exception, Cache cache) {
			logger.error(String.format("cacheName:%s", cache == null ? "unknown" : cache.getName()),
				exception);
			super.handleCacheClearError(exception, cache);
		}
	}
}
